
#include "../iface/LibSSH2Interface.h"
#include "../aprtools/APRFile.h"
#include "../aprtools/APRAutoLock.h"
#include "SCPSendChannel.h"

/** Initialisation for the secure copy channel, from local to remote.
 * 
 * @param remotefile The place on the remote file system to write to.
 * 
 * @param remotefilelength The length of the remote file to write in
 * bytes.
 * 
 * @param remoteaccess The octal access permissions for the remote
 * file.
 * 
 * @return The newly allocated SSH channel. */
LIBSSH2_CHANNEL *SCPSendChannel::initialiseSCPSend(const char *remotefile, int64_t remotefilelength, int remoteaccess)
{
   if (!remotefile) { errprint("remote file name is NULL"); return NULL; }
   if (tclient->isUp())
   {
      APRAutoLock autolock(tclient->getMutex());
      LIBSSH2_CHANNEL *tchannel = libssh2_scp_send64(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()), remotefile, remoteaccess, remotefilelength, time(NULL), time(NULL));
      if (tchannel == NULL)
      {
         elCheckLibSSH2Error(tclient);
         return NULL;
      }
      return tchannel;
   } else
   {
      errprint("SCPSendChannel (secure copy) can not be established - SSH client not connected");
      tclient = NULL;
      return NULL;
   }
}

/** Constructor for the secure copy channel.
 * 
 * @param itclient The SSH client object to use to create the secure
 * copy channel. */
SCPSendChannel::SCPSendChannel(SSHClient &itclient) : SSHChannel(itclient)
{
}

/** Destructor for the secure copy channel. */
SCPSendChannel::~SCPSendChannel()
{
   // This should not run until the future has finished - corruption
   // could result!
}

/** Copy the local file to a remote file with the given permissions.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param remotefile Name and path for the remote file.
 * 
 * @param localfile Name and path for the local file.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return Zero on success, non-zero on failure. */
int SCPSendChannel::copyFileToRemoteFile(LIBSSH2_CHANNEL *tchannel, const char *remotefile, const char *localfile, int remoteaccess)
{
   if (!remotefile) { errprint("remote file name is NULL"); return -1; }
   if (!localfile) { errprint("local file name is NULL"); return -1; }
   if (tchannel == NULL)
   {
      errprint("could not open SCP channel");
      return -1;
   }
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, localfile, APR_FOPEN_READ|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, imp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return -1; }
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   apr_off_t remaining = finfo.size;
   char *filebuffer = (char *)malloc(sizeof(char)*SCP_FILEBUFFER);
   while (remaining > 0)
   {
      apr_size_t realread = (remaining > SCP_FILEBUFFER) ? SCP_FILEBUFFER : remaining;
      apr_size_t fullread = realread;
      status = apr_file_read_full(newFP, filebuffer, realread, &fullread);
      CheckAPRError(status);
      if (fullread != realread)
      {
         errprint("error reading file '%s' - expected to read %" APR_SIZE_T_FMT ", actually read %" APR_SIZE_T_FMT " bytes.", localfile, realread, fullread);
         status = apr_file_close(newFP);
         CheckAPRError(status);
         free(filebuffer);
         return -1;
      }
      remaining -= fullread;
      ssize_t sofarwritten = 0;
      int timeout = 1;
      while (sofarwritten < realread)
      {
         ssize_t written = 0;
         {
            APRAutoLock autolock(tclient->getMutex());
            written = libssh2_channel_write_ex(tchannel, 0, &(filebuffer[sofarwritten]), SCP_FILEBUFFER - sofarwritten);
         }
         if ((written < 0) && (written != LIBSSH2_ERROR_EAGAIN))
         {
            {
               APRAutoLock autolock(tclient->getMutex());
               elCheckLibSSH2Error(tclient);
            }
            errprint("error writing to secure copy stream");
            status = apr_file_close(newFP);
            CheckAPRError(status);
            free(filebuffer);
            return -1;
         } else if (written == LIBSSH2_ERROR_EAGAIN)
         {
            timeout <<= 1;
            timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
            apr_sleep(timeout);
         } else if (written >= 0)
            sofarwritten += written;
      }
   }
   free(filebuffer);
   status = apr_file_close(newFP);
   CheckAPRError(status);
   return 0;
}

/** Copy the local buffer to a remote file with the given permissions.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param remotefile File in the remote filesystem to write to.
 * 
 * @param localbuffer Local buffer of data to write into the remote
 * file.
 * 
 * @param localbufferlen Length of the local buffer of data to write
 * into the remote file.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return Zero on success, non-zero on failure. */
int SCPSendChannel::copyBufferToRemoteFile(LIBSSH2_CHANNEL *tchannel, const char *remotefile, const char *localbuffer, int64_t localbufferlen, int remoteaccess)
{
   if (!remotefile) { errprint("remote file name is NULL"); return -1; }
   if (!localbuffer) { errprint("local buffer is NULL"); return -1; }
   if (tchannel == NULL)
   {
      errprint("could not open SCP channel");
      return -1;
   }
   ssize_t sofarwritten = 0;
   int timeout = 1;
   while (sofarwritten < localbufferlen)
   {
      ssize_t written = 0;
      {
         APRAutoLock autolock(tclient->getMutex());
         written = libssh2_channel_write_ex(tchannel, 0, &(localbuffer[sofarwritten]), localbufferlen - sofarwritten);
      }
      if ((written < 0) && (written != LIBSSH2_ERROR_EAGAIN))
      {
         {
            APRAutoLock autolock(tclient->getMutex());
            elCheckLibSSH2Error(tclient);
         }
         errprint("error writing to secure copy stream");
         return -1;
      } else if (written == LIBSSH2_ERROR_EAGAIN)
      {
         timeout <<= 1;
         timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
         apr_sleep(timeout);
      } else if (written >= 0)
         sofarwritten += written;
   }
   return 0;
}

/** Copy the local file to a remote file with the given permissions,
 * with the result as a future.
 * 
 * @param remotefile Name and path for the remote file.
 * 
 * @param localfile Name and path for the local file.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return The future which has an integer which is zero on success
 * and non-zero on failure. */
APRFuture<int> *SCPSendChannel::copyFileToRemoteFileFuture(const char *remotefile, const char *localfile, int remoteaccess)
{
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, localfile, APR_FOPEN_READ|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, imp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return NULL; }
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   status = apr_file_close(newFP);
   CheckAPRError(status);
   LIBSSH2_CHANNEL *tchannel = initialiseSCPSend(remotefile, finfo.size, remoteaccess);
   if (tchannel == NULL)
   {
      errprint("channel failed - SCP copy to remote file has no future");
      return NULL;
   }
   APRFuture<int> *remote_copy_result_future = new APRFuture<int>(tclient->getMemoryPool());
   remote_copy_result_future->addArgument((void *)this);
   remote_copy_result_future->addArgument(tchannel);
   remote_copy_result_future->addArgument(remotefile);
   remote_copy_result_future->addArgument(localfile);
   remote_copy_result_future->addArgument(remoteaccess);
   remote_copy_result_future->addFunctor(getCopyFileToRemoteFileThread());
   remote_copy_result_future->addDestructor(stopChannelFuture);
   remote_copy_result_future->start();
   return remote_copy_result_future;
}

/** Copy the local buffer to a remote file with the given permissions,
 * yielding a result as a future.
 * 
 * @param remotefile File in the remote filesystem to write to.
 * 
 * @param localbuffer Local buffer of data to write into the remote
 * file.
 * 
 * @param localbufferlen Length of the local buffer of data to write
 * into the remote file.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return A future containing a integer when complete. This is then
 * zero on success, non-zero on failure. */
APRFuture<int> *SCPSendChannel::copyBufferToRemoteFileFuture(const char *remotefile, const char *localbuffer, int64_t localbufferlen, int remoteaccess)
{
   LIBSSH2_CHANNEL *tchannel = initialiseSCPSend(remotefile, localbufferlen, remoteaccess);
   if (tchannel == NULL)
   {
      errprint("channel failed - SCP copy buffer to remote file has no future");
      return NULL;
   }
   APRFuture<int> *remote_copy_result_future = new APRFuture<int>(tclient->getMemoryPool());
   remote_copy_result_future->addArgument((void *)this);
   remote_copy_result_future->addArgument(tchannel);
   remote_copy_result_future->addArgument(remotefile);
   remote_copy_result_future->addArgument((void *)localbuffer, localbufferlen);
   remote_copy_result_future->addArgument(remoteaccess);
   remote_copy_result_future->addFunctor(getCopyBufferToRemoteFileThread());
   remote_copy_result_future->addDestructor(stopChannelFuture);
   remote_copy_result_future->start();
   return remote_copy_result_future;
}

/** A thread wrapper around the copy local file to remote file
 * function.
 * 
 * @param tthread Thread identifier from APR.
 * 
 * @param args Encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *SCPSendChannel::copyFileToRemoteFileThread(apr_thread_t *tthread, void *args)
{
   SCPSendChannel *kthis = (SCPSendChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *remotefile = (const char *)(((void **)args)[2]);
   const char *localfile = (const char *)(((void **)args)[3]);
   int remoteaccess = *((int *)(((void **)args)[4]));
   int *nret = new (int)();
   *nret = kthis->copyFileToRemoteFile(tchannel, remotefile, localfile, remoteaccess);
   return nret;
}

/** A thread wrapper around the copy local buffer to remote file
 * function.
 * 
 * @param tthread Thread identifier from APR.
 * 
 * @param args Encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *SCPSendChannel::copyBufferToRemoteFileThread(apr_thread_t *tthread, void *args)
{
   SCPSendChannel *kthis = (SCPSendChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *remotefile = (const char *)(((void **)args)[2]);
   const char *localbuffer = (const char *)(((void **)args)[3]);
   int64_t localbufferlen = (int64_t)(*((apr_off_t *)(((void **)args)[4])));
   int remoteaccess = *((int *)(((void **)args)[5]));
   int *nret = new (int)();
   *nret = kthis->copyBufferToRemoteFile(tchannel, remotefile, localbuffer, localbufferlen, remoteaccess);
   return nret;
}
